Incidenti aerei dal 1908 (Igor Fontanini, Gruppo X)¶

Questo dataset include:¶

  • Tutti gli incidenti dell'aviazione civile e commerciale di aerei di linea passeggeri di linea e non di linea in tutto il mondo, che hanno provocato un decesso (inclusi tutti gli incidenti mortali della parte 121 e 135 degli Stati Uniti)
  • Tutti gli incidenti mortali di carico, posizionamento, traghetto e volo di prova.
  • Tutti gli incidenti di trasporto militare con 10 o più morti.
  • Tutti gli incidenti con elicotteri commerciali e militari con più di 10 vittime.
  • Tutti gli incidenti di dirigibili civili e militari con vittime.
  • Incidenti aerei con la morte di personaggi famosi.
  • Incidenti aerei di notevole interesse.

link pagina dataset (https://www.kaggle.com/datasets/saurograndi/airplane-crashes-since-1908)

Questo notebook¶

  • È stato strutturato con domande a cui rispondere con alcuni grafici per poter avere possibli risposte (Cioè possibili considerazioni che si possono dedurre)

Dati¶

  • Date : Data dell'incidente
  • Time : Ora in cui è avvenuto l'incidente. Formato, in 24 ore, nell'ora locale se non specificato diversamente
  • Location : Luogo dell'incidente
  • Operator : Compagnia aerea o operatore dell'aeromobile
  • Flight : Numero di volo assegnato dall'operatore aereo
  • Route : Percorso completo o parziale effettuato prima dell'incidente
  • Type : Tipo di aereo
  • Registration : Registrazione del codice ICAO dell'aeromobile
  • cn/ln : Numero di costruzione o di serie / Numero di linea o di fusoliera
  • Aboard : Totale a bordo (passeggeri / equipaggio)
  • Fatalities : Vittime totali a bordo (passeggeri / equipaggio)
  • Ground : Totale uccisi a terra
  • Summary : Breve descrizione dell'incidente e causa se nota
In [1]:
import numpy as np
np.set_printoptions(precision=2)
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import calendar
df=pd.read_csv("data/Airplane_Crashes.csv")
#cn/ln --> Construction or serial number / Line or fuselage number
df
Out[1]:
Date Time Location Operator Flight # Route Type Registration cn/In Aboard Fatalities Ground Summary
0 09/17/1908 17:18 Fort Myer, Virginia Military - U.S. Army NaN Demonstration Wright Flyer III NaN 1 2.0 1.0 0.0 During a demonstration flight, a U.S. Army fly...
1 07/12/1912 06:30 AtlantiCity, New Jersey Military - U.S. Navy NaN Test flight Dirigible NaN NaN 5.0 5.0 0.0 First U.S. dirigible Akron exploded just offsh...
2 08/06/1913 NaN Victoria, British Columbia, Canada Private - NaN Curtiss seaplane NaN NaN 1.0 1.0 0.0 The first fatal airplane accident in Canada oc...
3 09/09/1913 18:30 Over the North Sea Military - German Navy NaN NaN Zeppelin L-1 (airship) NaN NaN 20.0 14.0 0.0 The airship flew into a thunderstorm and encou...
4 10/17/1913 10:30 Near Johannisthal, Germany Military - German Navy NaN NaN Zeppelin L-2 (airship) NaN NaN 30.0 30.0 0.0 Hydrogen gas which was being vented was sucked...
... ... ... ... ... ... ... ... ... ... ... ... ... ...
5263 05/20/2009 06:30 Near Madiun, Indonesia Military - Indonesian Air Force NaN Jakarta - Maduin Lockheed C-130 Hercules A-1325 1982 112.0 98.0 2.0 While on approach, the military transport cras...
5264 05/26/2009 NaN Near Isiro, DemocratiRepubliCongo Service Air NaN Goma - Isiro Antonov An-26 9Q-CSA 5005 4.0 4.0 NaN The cargo plane crashed while on approach to I...
5265 06/01/2009 00:15 AtlantiOcean, 570 miles northeast of Natal, Br... Air France 447 Rio de Janeiro - Paris Airbus A330-203 F-GZCP 660 228.0 228.0 0.0 The Airbus went missing over the AtlantiOcean ...
5266 06/07/2009 08:30 Near Port Hope Simpson, Newfoundland, Canada Strait Air NaN Lourdes de BlanSablon - Port Hope Simpson Britten-Norman BN-2A-27 Islander C-FJJR 424 1.0 1.0 0.0 The air ambulance crashed into hills while att...
5267 06/08/2009 NaN State of Arunachal Pradesh, India Military - Indian Air Force NaN Mechuka for Jorhat Antonov An-32 NaN NaN 13.0 13.0 0.0 The military transport went missing while en r...

5268 rows × 13 columns

Completezza delle colonne¶

Analisi per decidere che colonne prendere in considerazione e quindi decidere quanta importanza dargli.

In [2]:
dfc = pd.DataFrame({"column" : df.columns})

dfc["notna"] = dfc["column"].apply(lambda c: df.loc[df[c].notna(), c].count()*100/len(df.index))
dfc = dfc.sort_values(by='notna')

hc = px.histogram(dfc, orientation="h", y='column', x='notna', height= 500)
hc.layout["title"] = "Percentuale dati validi nel dataset per ogni colonna"
hc.layout["yaxis"]["title"] = "Colonne"
hc.layout["xaxis"]["title"] = "righe non NaN in percentuale"
hc.update_layout(bargap=0.5)
hc.show()

Prime modifiche al dataset¶

In [3]:
#Vengono divise le date per poter differenziare tra giorno, mese, anno
df["day"] = pd.DatetimeIndex(df["Date"]).day 
df["month"] = pd.DatetimeIndex(df["Date"]).month
df["year"] = pd.DatetimeIndex(df["Date"]).year
df["total_fatalities"] = df["Fatalities"] + df["Ground"] #Calcolo delle morti totali causate dall'incidente

Incidenti nel tempo¶

Numero di incidenti negli anni¶

Domanda : Come si evolvono negli anni gli incidenti?

In [4]:
timeline_s = df.groupby("year")["year"].count()
timeline_crashes = px.line(timeline_s, 
        title=f"Numero di incidenti negli anni ({timeline_s.index.min()} - {timeline_s.index.max()})",
        labels={"value": "Numero di incidenti", "index": "Anno"})
timeline_max = timeline_s.idxmax()
timeline_crashes.add_vline(x=timeline_max, line_width=1, line_color="red", annotation_text=f"{timeline_max} - {timeline_s.max()} incidenti")
timeline_crashes.update_layout(showlegend=False, font=dict(size= 16))

Risposta : Si può vedere che gli incidenti aumentano negli anni, perché sono sempre di più i velivoli e voli di linea che vengono effettuati. Dopo il picco nel 1972 si può notare che tendono a diminuire molto probabilmente perché sono state sviluppate negli anni nuove tecnologie e tecniche per poter evitare gli incidenti.

Numero di incidenti medi mensili¶

Domanda : Quale è la media mensile di incidenti del dataset, quale è il periodo dell'anno più critico?

In [5]:
dfByMonth = pd.DataFrame(pd.DataFrame(df.groupby(["year","month"]).size().reset_index(name='count')).groupby(["month"]).mean())
dfByMonth["month_name"] = dfByMonth.index
dfByMonth["month_name"] = dfByMonth["month_name"].apply(lambda x: calendar.month_name[x])

color = ['blue'] * 12
color[0] = 'darkblue'
color[11] = 'darkblue'
crashesDistributionByMonthH = go.Figure(data=[go.Bar(x=dfByMonth["month_name"], y=dfByMonth["count"], marker_color=color)])
crashesDistributionByMonthH.layout["title"] = "Numero di incidenti medi mensili (calcolato su tutti gli anni)"
crashesDistributionByMonthH.layout["xaxis"]["title"] = "Mese"
crashesDistributionByMonthH.layout["yaxis"]["title"] = "incidenti medi al mese"
crashesDistributionByMonthH.update_layout(height=500, bargap=0.5, font=dict(size= 16))
crashesDistributionByMonthH.show()

Risposta : Non ci sono valori molto sopra gli altri, ma comunque si può notare che Gennaio e Dicembre sono un po' più alti del normale. Questo potrebbe essere causato dal fatto che i dati potrebbero essere concentrati nell’emisfero Nord del pianeta. Quindi in quel periodo dell’anno la meteo è molto critica per gli aerei.

Operatori¶

Operatori con più incidenti¶

Domanda : Quali sono gli operatori meno affidabili?

In [6]:
gbOperator = df.groupby("Operator")
dfOperator = gbOperator.sum(["Aboard", "Fatalities", "Ground", "total_fatalities"]) #Per eventuali analisi future
dfOperator["n_crashes"] = gbOperator["Operator"].count()
dfOperator["mortality_aboard"] = dfOperator["Fatalities"]*100/dfOperator["Aboard"]

mostCrashesOperators = dfOperator.sort_values("n_crashes").tail(10)

crashesPercentage = mostCrashesOperators['n_crashes'].sum()*100/len(df)

topOperatorsMostCrashesH = px.histogram(mostCrashesOperators, 
             y = mostCrashesOperators.index, 
             x = "n_crashes",
             title=f"I 10 Operatori con il maggior numero di incidenti (il {crashesPercentage:.1f}% di incidenti su {len(dfOperator)} operatori) ",
             height = 600,
             text_auto=True
            )
topOperatorsMostCrashesH.layout["yaxis"]["title"] = "Operatore"
topOperatorsMostCrashesH.layout["xaxis"]["title"] = "Numero di incidenti"
topOperatorsMostCrashesH.update_layout(bargap=0.4, font=dict(size= 16))
topOperatorsMostCrashesH.show()

Risposta : Come si può vedere in cima alla classifica c’è l’operatore russo Aeroflot con subito dopo l’Air Force Statunitense.

Domanda : Si nota però che i militari americano appaiano già più volte nella classifica sottosezioni diverse dell’esercito. Quindi quanti sono veramente gli incidenti dei militari americani o altri tipi di militari?

In [7]:
dfOperator["military_type"] = "NON MILITARE"
dfOperator.loc[dfOperator.index.str.lower().str.contains("military"), "military_type"] = "ALTRI"
dfOperator.loc[dfOperator.index.str.lower().str.contains("military - u.s."), "military_type"] = "USA"

pieMilitaryType = px.pie(dfOperator, values='n_crashes', names='military_type', title = "Percentuale incidenti operatori militari rispetto agli altri")
pieMilitaryType.update_traces(textposition='inside', textinfo='label+percent')
pieMilitaryType.update_layout(showlegend=False,width=700,height=700, font=dict(size= 16))
pieMilitaryType.show()

Risposta : I militari americani hanno fatto 321 incidenti (rispetto ai 179 di Aeroflot). In totale gli incidenti di operatori militari sono il 15% del dataset.

Tipi di velivoli¶

Velivoli con il maggior numero di incidenti¶

Domanda : Quali sono i velivoli meno affidabili?

In [8]:
gbType = df.groupby("Type")
dfType = gbType.sum(["Aboard", "Fatalities", "Ground", "total_fatalities"]) #Per eventuali analisi future
dfType["n_crashes"] = gbType["Type"].count()
dfType["mortality_aboard"] = dfType["Fatalities"]*100/dfType["Aboard"]

mostCrashesTypes = dfType.sort_values("n_crashes").tail(10)
topTypesMostCrashesH = px.histogram(mostCrashesTypes, 
             y = mostCrashesTypes.index, 
             x = "n_crashes",
             title="I 10 Velivoli con il maggior numero di incidenti",
             height = 600,
             text_auto=True
            )
topTypesMostCrashesH.layout["yaxis"]["title"] = "Velivolo"
topTypesMostCrashesH.layout["xaxis"]["title"] = "Numero di incidenti"
topTypesMostCrashesH.update_layout(bargap=0.4, font=dict(size=16))
topTypesMostCrashesH.show()

Risposta : Come si può vedere in cima alla classifica spicca il Douglas DC-3, si potrebbe dire che è un velivolo poco affidabile. O forse potrebbe anche essere perché era uno dei velivoli più utilizzati e per questo ha avuto più incidenti rispetto agli altri.

Domanda : Comunque i velivoli di tipo Douglas appaiano spesso nella classifica. Quindi quanto spazio occupa nel numero d'incidenti rispetto ad altri importanti produttori di aerei?

In [9]:
#codice per capire possibli produttori con molti incidenti
possiblePlaneTypes = dict()
for t in df.loc[df["Type"].notna(), "Type"]:
    for n in t.split():
        name = n.lower()
        if name in possiblePlaneTypes:
            possiblePlaneTypes[name] += 1
        else:
            possiblePlaneTypes[name] = 1
dfPossiblePlaneTypes = pd.DataFrame.from_dict(possiblePlaneTypes, orient='index', columns=['n_crashes']) 
dfPossiblePlaneTypes.loc[dfPossiblePlaneTypes["n_crashes"] > 300] # compagnie validi cessna, boeing, douglas, lockheed
Out[9]:
n_crashes
de 301
douglas 1113
boeing 384
lockheed 343
dc-3 376
cessna 307
In [10]:
dfType["company"] = "ALTRI" 
dfType.loc[dfType.index.str.lower().str.contains("cessna"), "company"] = "CESSNA"
dfType.loc[dfType.index.str.lower().str.contains("boeing"), "company"] = "BOEING"
dfType.loc[dfType.index.str.lower().str.contains("douglas"), "company"] = "DOUGLAS"
dfType.loc[dfType.index.str.lower().str.contains("lockheed"), "company"] = "LOCKHEED"

pieDouglasType = px.pie(dfType, values='n_crashes', names='company', title = "Percentuale incidenti dei produttori")
pieDouglasType.update_traces(textposition='inside', textinfo='label+percent')
pieDouglasType.update_layout(showlegend=False,width=700,height=700, font=dict(size= 16))
pieDouglasType.show()

Risposta : Il produttore Douglas è quello che ha la percentuale maggiore di incidenti. Quelli che lo seguono in numero di incidenti sono altri importanti produttori. Quindi sembra più plausibile l’idea che abbiano più incidenti perché sono stati quelli più utilizzati come tipi di velivoli.

Decessi¶

Quantità di decessi distributi nel tempo¶

Domanda : C'è una differenza della distribuzione nel tempo di incidenti la cui la quantità dei morti rispetto a quelli a bordo è differente? Magari per riuscire a capire se nel tempo i velivoli siano diventati più sicuri, cioè che nonostante l’incidente i passeggeri si possano salvare?

In [11]:
df["aboard_fatalities_type"] = "tutti"
df.loc[df["Fatalities"] < (df["Aboard"]/2), "aboard_fatalities_type"] = "alcuni"
df.loc[df["Fatalities"] >= (df["Aboard"]/2), "aboard_fatalities_type"] = "molti"
df.loc[df["Fatalities"] == 0, "aboard_fatalities_type"] = "nessuno"

boxAboardFatalitiesType = px.box(df, 
       orientation="h", 
       y="aboard_fatalities_type", 
       x="year", 
       category_orders={"aboard_fatalities_type": ["tutti", "molti",  "alcuni", "nessuno"]}, 
       title="Quantità di decessi distributi nel tempo",
       height=500,
       labels={"aboard_fatalities_type" : "passeggeri morti", "year" : "anno"})
boxAboardFatalitiesType.update_layout(font=dict(size= 16))
boxAboardFatalitiesType.show()

Risposta : Sembra proprio che con l'avanzare del tempo sia diventato più “facile” sopravvivere ad un eventuale incidente. Probabilmente grazie al miglioramento della sicurezza degli aerei anche in caso di incidente. Magari anche grazie ad una migliore formazione dei piloti in casi di emergenza.

Posizione¶

Codice per trovare la posizione in coordinate (Attenzione: avviare solo la prima volta)¶

Questo codice trova le coordinate per ogni posizione tramite la stringa nella colonna "Location", questo grazie ad un geolocator. Visto che richiede molto tempo viene salvato in un file quindi non è necessario avviarlo se non la prima volta.

In [12]:
from geopy.adapters import AioHTTPAdapter
from geopy.extra.rate_limiter import AsyncRateLimiter
from geopy.geocoders import Nominatim #risorsa online per trovare le posizione in latitudine e longitudine

firstTime = False #Mettere a true per eseguire il codice del geolocator

if firstTime:
    dfLatLon=pd.read_csv("data/Airplane_Crashes.csv")
    dfLatLon["year"] = pd.DatetimeIndex(dfLatLon["Date"]).year

    async with Nominatim(
        user_agent="airplane_crashes",
        adapter_factory=AioHTTPAdapter,
    ) as geolocator:
        geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1)
        dfLatLon["lat_lon"] = [await geocode(s) for s in dfLatLon["Location"]]
    dfLatLon = dfLatLon.loc[dfLatLon["lat_lon"].isnull() == False]
    dfLatLon["latitude"] = dfLatLon["lat_lon"].apply(lambda x: x.latitude)
    dfLatLon["longitude"] = dfLatLon["lat_lon"].apply(lambda x: x.longitude)
    #dfLatLon.to_csv('data/Airplane_Crashes_Lat_Lon.csv') # ! togliere da commento per salvare

Mappa con tutte le posizioni¶

Domanda : Come si distribuiscono i vari incidenti per il globo?

In [13]:
dfLatLon=pd.read_csv("data/Airplane_Crashes_Lat_Lon.csv")
mb_crashes = px.scatter_mapbox(dfLatLon, lat="latitude", lon="longitude", title=f"Distribuzione incidenti (posizioni trovate {len(dfLatLon)} su {len(df)})", hover_name='Location', hover_data=["Summary"], zoom=1, height=1000)
mb_crashes.update_layout(mapbox_style="open-street-map")
mb_crashes.show()

Risposta : Gli incidenti sembrano concentrarsi in America e Europa. Però non è possibile dirlo con certezza anche perché, purtroppo, il geolocator ha solo trovato circa 3000 sulle circa 5000 posizione totali.

Domanda : Quindi è necessario ripensare a come fare il grafico?

In [14]:
mb_crashes_anim = px.scatter_mapbox(dfLatLon, lat="latitude", lon="longitude", title="Distribuzione incidenti nel tempo (animato)", hover_name='Location', hover_data=["Summary"], animation_frame="year", animation_group="year",  zoom=1, height=1000)
mb_crashes_anim.update_layout(mapbox_style="open-street-map")
mb_crashes_anim.show()

Codice per trovare la posizione degli stati in coordinate (Attenzione: avviare solo la prima volta)¶

Questo codice funziona molto similmente a quello di prima solo che lo trova per lo stato/nazione definito come parametro più a destra nella colonna “Location”. Questo permette di trovare più posizioni (più di 5000) rispetto a prima che però saranno meno precise.

In [15]:
if firstTime:
    df["state"] = df["Location"].str.split(', ').str[-1]
    states_df = df.groupby("state").size().reset_index(name='count')
    async with Nominatim(
        user_agent="airplane_crashes",
        adapter_factory=AioHTTPAdapter,
    ) as geolocator:
        geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1)
        states_df["location"] = [await geocode(s) for s in states_df["state"]]
    states_df = states_df.loc[states_df["location"].isnull() == False]
    states_df["latitude"] = states_df["location"].apply(lambda x: x.latitude)
    states_df["longitude"] = states_df["location"].apply(lambda x: x.longitude)
    #states_df.to_csv('data/States_Airplane_Crashes.csv') # ! togliere da commento per salvare

Mappa con tutte le posizioni degli stati¶

In [16]:
states_df=pd.read_csv("data/States_Airplane_Crashes.csv") #togliere commento se si è perso il contenuto del dataframe

mb_crashes = px.scatter_mapbox(states_df, lat="latitude", lon="longitude", title=f"Distribuzione incidenti (posizioni trovate {states_df['count'].sum()} su {len(df)})", size="count", color="count", hover_name='state', zoom=1, height=900, labels={"count" : "numero incidenti"})
mb_crashes.update_layout(mapbox_style="open-street-map")
mb_crashes.show()

Risposta : Si può ancora vedere una concentrazione in America e Europa nonostante il numero maggiore di posizioni (tradotte in grandezze del punto).

In [ ]: